package task;

import app.FileMeta;
import callback.ScanCallBack;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author yuisama
 * @date 2022/06/11 18:01
 * 文件扫描任务类
 **/
public class FileScanner {
    // 当前系统可用CPU数
    private static final int COUNT = Runtime.getRuntime().availableProcessors();

    // 工作队列 - 基于链表的无界阻塞队列
    private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    /**
     * 扫描任务线程池
     * 固定大小线程池,大小为当前系统可用CPU数，保证并行，超过当前可用CPU的任务交给调用线程执行
     */
    private final ExecutorService threadPool = Executors.newFixedThreadPool(COUNT * 2);

    private final AtomicInteger files = new AtomicInteger();

    private final AtomicInteger dirs = new AtomicInteger();

    private final AtomicInteger threadCount = new AtomicInteger();
    /**
     * 方式一，使用wait和notify
     */

    private final Object lock = new Object();
    /**
     * 方式二使用同步计数器来等待所有扫描任务结束
     */
    private final CountDownLatch latch = new CountDownLatch(1);

    /**
     * 方式三使用Semaphore信号量来等待所有扫描任务结束
     */
    private final Semaphore semaphore = new Semaphore(0);

    // 文件扫描回调接口
    private ScanCallBack callBack;

    // 缓存当前扫描到的所有文件信息
    private final List<FileMeta> locals = new ArrayList<>();

    public FileScanner(ScanCallBack callBack) {
        this.callBack = callBack;
    }

    /**
     * 递归扫描当前路径下的所有文件以及文件夹
     * @param root
     */
    public void scan(File root) {
        long start = System.nanoTime();
        System.out.println("开始执行文件扫描任务，根路径为:" + root);
        threadCount.incrementAndGet();
        scanInternal(root);
        // 等待所有扫描任务处理完毕
        try {
//            // 方式1
//            synchronized (lock) {
//                lock.wait();
//            }
            // 方式2
//            latch.await();
            // 方式3
            semaphore.acquire();
            long end = System.nanoTime();
            System.out.println(root + "路径下的文件扫描完毕，共耗时 : " + (end - start) / 1000000.0 + "ms");
            System.out.println("共扫描到 : " + files.get() + "个文件");
            System.out.println("共扫描到 : " + dirs.get() + "个文件夹");
            // TODO 扫描所有文件信息后统一写库
            System.out.println("开始写入数据库");
            start = System.nanoTime();
            this.callBack.save2DB(locals);
            end = System.nanoTime();
            System.out.println("写入数据库完成，共耗时 : " + (end - start) / 1000000.0 + "ms");
        } catch (InterruptedException e) {
            System.err.println("中断当前的扫描任务");
        } finally {
            // 无论当前任务是否正确执行完毕，都要关闭线程池
            System.out.println("关闭线程池..");
            threadPool.shutdownNow();
        }
    }

    /**
     * 递归扫描所有文件夹内的文件
     * @param path
     */
    public void scanInternal(File path) {
        threadPool.execute(() -> {
            try {
                // 当前线程先把文件夹下的所有信息写入db
                // 使用回调接口将下一级文件夹和文件信息持久化到db
                // 将所有文件扫描结束之后统一写库
//                this.callBack.callback(path);
                File[] child = path.listFiles();
                for (File file : child) {
                    if (file.isDirectory()) {
//                        System.out.println("文件夹 : " + file);
                        // 新启线程去扫描子文件夹
                        threadCount.incrementAndGet();
                        dirs.incrementAndGet();
                        this.locals.add(new FileMeta(file));
                        scanInternal(file);
                    } else {
                        // 当前扫描到文件,当前线程继续执行下一个文件扫描
//                        System.out.println("文件 : " + file);
                        this.locals.add(new FileMeta(file));
                        files.incrementAndGet();
                    }
                }
            }finally {
                // 每当有一个线程把当前文件夹处理完毕，任务数 - 1
                threadCount.decrementAndGet();
                System.out.println(Thread.currentThread().getName() + ":" + path + "扫描完毕");
                if (threadCount.get() == 0) {
                    // 所有任务处理完毕
//                    // 方式1
//                    synchronized (lock) {
//                        lock.notify();
//                    }
                    // 方式2
//                    latch.countDown();
                    // 方式3
                    semaphore.release();
                }
            }
        });
    }
}
